package library;

import java.util.Calendar;
import java.util.HashMap;
import java.util.List;

import udp.UdpClient;
import udp.message.GetNonReturnersMessage;
import udp.message.UdpMessage;
import util.ConfigurationHelper;
import dom.AdminAccount;
import dom.Book;
import dom.BookReservation;
import dom.EducationalSystemEnum;
import dom.StudentAccount;
import exception.InvalidCredentialsException;
import exception.UnexistentStudentAccountException;

/**
 * Manages the services provided by the library system
 * @author Alex
 *
 */
public class LibraryServerService {

	private Library library;
	private boolean debugMode = false;
	private LibraryUtil libraryUtil = new LibraryUtil();
	
	public LibraryServerService(Library library) {
		this.library = library;
		debugMode = Boolean.parseBoolean(ConfigurationHelper.getProperty("debug"));
	}
	
	/**
	 * Checks if the username and password are found in the database
	 * @param username
	 * @param password
	 * @return
	 * @throws UnexistentStudentAccountException
	 * @throws InvalidCredentialsException
	 */
	private boolean authenticate(String username, String password) throws UnexistentStudentAccountException, InvalidCredentialsException {
		
		boolean authenticated = false;

		if(username.isEmpty()){
			throw new IllegalArgumentException("username cannot be empty");
		}
		
		String key = username.substring(0,1);
		StudentAccount account = null;
		HashMap<String, List<StudentAccount>> accounts = library.getAccounts();
		List<StudentAccount> letterSpecificAccounts = null;
		
		if(accounts.get(key) == null){
			throw new UnexistentStudentAccountException("The student account with username " + username + " doesn't exist");
		}
		else{
			letterSpecificAccounts = accounts.get(key);
		}
		
		account = new StudentAccount(username, password);
		
		for(StudentAccount sa : letterSpecificAccounts){
			//debug("Does " +  sa.getUsername() + "==" + account.getUsername() + "?");
			if(sa.equals(account)){
				if(account.getPassword().equalsIgnoreCase(sa.getPassword())){
					//debug("Does " +  sa.getPassword() + "==" + account.getPassword() + "?");
					account = sa;
					//System.out.println("Found the user");
					account.toString();
					authenticated = true;
				}
			}
		}
		
		if(!authenticated){
			//System.out.println(username + " " + password);
			//throw new InvalidCredentialsException("The username and password combination is invalid");
		}
		
		return authenticated;
	}
	
	/**
	 * Creates an account
	 * @param firstName
	 * @param lastName
	 * @param emailAddress
	 * @param phoneNumber
	 * @param username
	 * @param password
	 * @param educationalSystemId
	 * @return
	 */
	public String createAccount(String firstName, String lastName, String emailAddress, String phoneNumber, String username, String password, String educationalSystemId) {

		boolean added = false;
		
		if(username.length() < 6 || username.length() > 15){
			print("createAccount(): username must be between 6 and 15 characters long");
			return "username must be between 6 and 15 characters long";
		}
		
		// TODO: check if the account already exists by contacting all the other library servers (yeah right)
		StudentAccount studentAccount = new StudentAccount(firstName, lastName, emailAddress, phoneNumber, username, password, EducationalSystemEnum.valueOf(educationalSystemId.trim().toUpperCase()));
		
		HashMap<String, List<StudentAccount>> accounts = library.getAccounts();
		
		try{
			if(libraryUtil.studentExistsInAccountList(studentAccount,accounts)){
				print("createAccount(): account with username " + username + " already exists");
				return "An account with username [" + username + "] already exists";
			}
			else{
				added = libraryUtil.addStudentAccountToAccountList(studentAccount,accounts);
			}
		}
		catch(Exception ex){
			print("Error in create account: " + ex.getMessage());
			ex.printStackTrace();
			return "Couldn't create account";
		}

		if(added){
			library.exportAccounts(accounts);
			print("added student account " + studentAccount.toString());
			return "added student account " + studentAccount.getUsername();
		}
		else{
			return "Failed to create account";
		}
		
	}
	
	/**
	 * Reserves a book from the library if it exists and if there are copies of the book left
	 * @param username
	 * @param password
	 * @param bookName
	 * @param authorName
	 * @return
	 */
	public String reserveBook(String username, String password, String bookName, String authorName) {
		
		boolean authenticated = false;
		
		if(username.isEmpty()){
			throw new IllegalArgumentException("username cannot be empty");
		}
		
		if(password.isEmpty()){
			throw new IllegalArgumentException("password cannot be empty");
		}
		
		if(bookName.isEmpty()){
			throw new IllegalArgumentException("book name cannot be empty");
		}
		
		if(authorName.isEmpty()){
			throw new IllegalArgumentException("author name cannot be empty");
		}
		
		try{
			authenticated = authenticate(username,password);
		}
		catch(Exception e){
			print(e.getMessage());
			authenticated = false;
		}
		
		if(!authenticated){
			print("reserveBook(): invalid login attempt : " + username + " " + password);
			//throw new InvalidCredentialsException("The username and password combination are invalid");
			return "The username and password combination are invalid";
		}
		
		
		//TODO need to remove this and get the entire list of accounts
		StudentAccount account = library.getAccountByUsername(username);
		
		if(this.library.getBooks().size() <= 0){
			print("reserveBook(): no copies left of "+bookName);
			//throw new Exception("There are no books in the " + this.library.getEducationalInstitutionString() + " library");
			return "There are no books in the " + this.library.getEducationalInstitutionString() + " library";
		}
		
		// Go get the book in the library
		Book bookToReserve = library.getBookByName(bookName);
		
		if(bookToReserve == null){
			print("reserveBook(): "+bookName +" doesnt not exist");
			//throw new Exception("The book " + bookName + " does not exist in the " + this.library.getEducationalInstitutionString() + " library");
			return "The book " + bookName + " does not exist in the " + this.library.getEducationalInstitutionString() + " library";
		}
		
		if(authorName.equalsIgnoreCase(bookToReserve.getAuthor())){
			//throw new Exception("The author you provided doesn't match");
			print("reserveBook(): the author provided does not match the author of the book");
		}
			
		//TODO : remove this synchronize(this) block and replace it with something else
		
		synchronized(this){	
			if(bookToReserve.getCopies() <= 0){
				print("reserveBook(): No copies of '" + bookName + "' left");
				//throw new Exception("No copies of '" + bookName + "' left in the " + this.library.getEducationalInstitutionString() + " library");
				return "No copies of '" + bookName + "' left in the " + this.library.getEducationalInstitutionString() + " library";
			}
			
			BookReservation bookReservation = new BookReservation(bookToReserve);
			//library.addBookReservation(bookReservation);
			account.addBookReservation(bookReservation);
			bookToReserve.setCopies(bookToReserve.getCopies()-1);
			library.saveAccounts();
			library.saveBooks();
			print("reserveBook(): added " + bookReservation.toString());
			print("reserveBook(): there are now " + bookToReserve.getCopies() + " copies of '" + bookToReserve.getName() + "' left");
			//print("--------------------------------------------------\n"+library.listBookReservations() + "\n--------------------------------------------------");
		}
		
		//library.exportAccounts(accounts);
		
		return "Reserved book";
	}

	/**
	 * Returns a list of books found in the libraries database
	 * @param username
	 * @param password
	 * @return
	 */
	public String getBookList(String username, String password) {
	
		try{
			authenticate(username, password);
		}
		catch(Exception ex){
			System.out.println("Exception occured :" + ex.getMessage());
			return "false";
		}
		
		List<Book> books = library.getBooks();
		StringBuilder sb = new StringBuilder();
		for(Book book : books){
			sb.append(book.getName() + "\t" + book.getAuthor() + "\t" + book.getCopies() + "\n");
		}
		return sb.toString();
	}
	
	/**
	 * Retrieves a list of users that have books that are overdue
	 * @param adminUsername
	 * @param adminPassword
	 * @param edu
	 * @param numDays
	 * @return
	 */
	public String getNonReturners(String adminUsername, String adminPassword, String edu, int numDays, boolean localOnly) {

		print("got into getNonReturners function from [" + edu+ "] library service");
		
		// Validate the username and password with the library
		boolean validated = false;
		for (AdminAccount acc : library.getAdmins()){
			if(acc.getUsername().equals(adminUsername)){
				if(acc.getPassword().equals(adminPassword)){
					validated = true;
				}
			}
		}
		
		if(!validated){
			//TODO : re-enable this
			print("Invalid credentials");
			return "The username and password combination is incorrect";
			//throw new InvalidCredentialsException("The admin username and password combination is invalid");
		}
		
		print("right before build non returners string function on the in libraryutil");
		String nonReturnersToReturn = libraryUtil.buildNonReturnersString(library.getNonReturners());
		
		if(!localOnly){
			print("!localOnly :: must send the message to the other libraries");
			// mean if it was the first time the message was received then you must send to other libraries
			for(EducationalSystemEnum key : LibraryProperties.getUDPPorts().keySet()){
				if(!key.toString().equals(library.getEducationalInstitutionString())){
					try{
						GetNonReturnersMessage getNonReturnersMessage = new GetNonReturnersMessage(adminUsername, adminPassword, numDays, key.toString());
						getNonReturnersMessage.setFirst(false);
						//getNonReturnersMessage.setEi(edu);
						getNonReturnersMessage.setDestHost(LibraryProperties.getUDPHosts().get(key));
						getNonReturnersMessage.setDestLibrary(key.toString());
						getNonReturnersMessage.setSrcLibrary(library.getEducationalInstitutionString());
						// Get the destination UDP info
						String host = LibraryProperties.getUDPHosts().get(key);
						int port = LibraryProperties.getUDPPorts().get(key);
						UdpClient client = new UdpClient(host,port);
						print("sending GetNonReturnersMessage to [" + host + ":" + port + "]");
						// Set where the messag is coming from
						getNonReturnersMessage.setSrcHost(LibraryProperties.getHostnameById(library.getEducationalInstitutionString()));
						getNonReturnersMessage.setSrcPort(LibraryProperties.getPortById(library.getEducationalInstitutionString()));
						// send and receive
						UdpMessage genericReply = client.sendAndReceive(getNonReturnersMessage);
						print("receive a reply from [" + host + ":" + port + "]");
						GetNonReturnersMessage reply;
						if(genericReply instanceof GetNonReturnersMessage){
							reply = (GetNonReturnersMessage) genericReply;
							nonReturnersToReturn += reply.getResult();
						}
						
					}
					catch(Exception ex){
						print("Couldn't send message: " + ex.getMessage());
					}
				}
			}
		}
		
		String tmp = "returning nonReturners: \n";
		tmp += nonReturnersToReturn;
		tmp += "\n";
		print(tmp);
		
		return nonReturnersToReturn;
	}
	
	/**
	 * Reserves a book in a remote library
	 * @param username
	 * @param password
	 * @param bookName
	 * @param authorName
	 * @return
	 */
	public String reserveInterLibrary(String username, String password, String bookName, String authorName) {

		print("Attempting to reserver " + bookName + " locally");
		// return since the book was found in the local library
		if(reserveBook(username, password, bookName, authorName).equals("Reserved book")){
			return "Reserved book";
		}
		
		StudentAccount account = library.getAccountByUsername(username);
		
		// Was not in the local library must check with other libraries
		print("Couldn't find book locally");
		String message = "INTERBOOK;" + username + "," + password + "," + bookName + 
				"," + authorName + "," + account.getFirstName() + "," + account.getLastName() + 
				"," + account.getEmailAddress() + "," + account.getPhoneNumber() + 
				"," + account.getEdu().toString();
		System.out.println(message);
		UdpMessage request = new UdpMessage(message.getBytes(), 1, 123,LibraryProperties.getPortById(library.getEducationalInstitutionString()));
		String reservationNotice = "";
		boolean found = false;
		
		// TODO: re-enable this
/*		
		for(EducationalSystemEnum key : UDPPorts.keySet()){
			// Make sure you don't send the data to yourself
			UdpMessage reply = null;
			if(!key.toString().equals(library.getEducationalInstitutionString())){
				try{
					reply = sendUDP(key.toString(),LibraryProperties.getPortById(key.toString().toLowerCase()),request);
					String bookReserved = new String(reply.getPacket(), 0, reply.getPacket().length);
					if(bookReserved.equalsIgnoreCase("true")) {
						found = true;
					}
					if(found){
						print("reserved the book " + bookName + " from [" + key.toString() + "] library");
						return "true";
						//break;
					}
				}
				catch(Exception ex){
					print("Couldn't reserve interlibrary: " + ex.getMessage());
				}
			}
		}
	*/
		return "false";
	}
	
	/**
	 * Sets the duration of a specific book reservation to numDays
	 * @param username
	 * @param bookName
	 * @param numDays
	 */
	public String setDuration(String username, String bookName, int numDays) {
		debug("setting duration [" + username + "," + bookName + "," + numDays + "]");
		//StudentAccount account = library.getAccountByUsername(username);
		List<BookReservation> bookReservations = library.getBookReservations();
		for(BookReservation bookReservation : bookReservations){
			if(bookReservation.getStudentAccount().getUsername().equalsIgnoreCase(username)){
				debug("Found a reservation with the username " + username);
				if(bookReservation.getBook().getName().equalsIgnoreCase(bookName)){
					print("Setting duration to -" + numDays + " days for book " + bookName);
					Calendar cal = Calendar.getInstance();
					cal.add(Calendar.DAY_OF_YEAR, -(numDays));
					bookReservation.setDueDate(cal.getTime());
					return "true";
				}
				else return "false";
			}
			else return "false";
		}
		return "false";
	}
	
	
	public Library getLibraryDatabase() {
		return this.library;
	}
	
	public void setLibraryDatabase(Library library) {
		this.library = library;
	}
	
	/**
	 * Prints to console
	 * @param msg
	 */
	private void print(String msg){
		System.out.println("[" + library.getEducationalInstitutionString() + "] " + msg);
	}
	
	/**
	 * Checks if the server is in debug mode before printing to console
	 * @param msg
	 */
	private void debug(String msg){
		if(debugMode){
			print(msg);
		}
	}

}
